"""
基于波士顿房屋数据训练训练线性回归模型
"""
import numpy as np
import matplotlib.pyplot as plt


def load_data():
    """
    加载数据
    1. 读取数据
    2. 数据处理
    3. 划分训练数据和测试数据
    :return:
    """
    # 读取到的数据是一维数据，需要转换为二维数据
    data = np.fromfile('housing.data', sep=' ')
    # print(data.shape)

    # 定义特征名称
    feature_names = [ 'CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE','DIS',
                     'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV' ]
    # 确定特征的数量
    feature_num = len(feature_names)

    # 对数据进行变维 data.shape[0]//feature_num：每行14个，算出可以划分的整数行
    data = data.reshape([data.shape[0]//feature_num,feature_num])

    # 划分训练集和测试集
    # 定义划分比例
    ratio = 0.8
    offset = int(data.shape[0] * ratio)
    train_data = data[:offset]

    # 对训练集进行归一化处理
    maximums,minimums = train_data.max(axis=0),train_data.min(axis=0)
    for i in range(feature_num):
        data[:,i] = (data[:,i] - minimums[i])/(maximums[i] - minimums[i])

    # 划分训练集和测试集
    train_data = data[:offset]
    test_data = data[offset:]
    return train_data,test_data

class Network(object):
    """
    定义神经网络对象
    """
    def __init__(self,num_of_weights):
        np.random.seed(0)
        self.w = np.random.randn(num_of_weights,1)
        self.b = 0.

    def forward(self,x):
        """
        定义线性模型
        :param x: 训练数据
        :return:
        """
        z = np.dot(x,self.w) + self.b
        return z

    def loss(self,z,y):
        """
        定义损失函数，均方误差
        :param z: 预测值
        :param y:  真实值
        :return:
        """
        error = z - y
        cost = error * error
        cost = np.mean(cost)
        return cost

    def gradient(self,x,y):
        """
        梯度下降方法
        :param x:  训练数据
        :param y:  真实数据
        :return:
        """
        z = self.forward(x)
        gradient_w = (z - y) * x
        gradient_w = np.mean(gradient_w,axis=0)
        gradient_w = gradient_w[:,np.newaxis]

        gradient_b = z - y
        gradient_b = np.mean(gradient_b)

        return gradient_w,gradient_b

    def update(self,gradient_w,gradient_b,eta=0.01):
        """
        更新权重和偏置
        :param gradient_w: 梯度下降权重
        :param gradient_b: 梯度下降偏置
        :param eta:
        :return:
        """
        self.w = self.w - gradient_w * eta
        self.b = self.b - gradient_b * eta

    def train(self,x,y,iteration=100,eta=0.01):
        """
        开始训练模型
        :param x: 训练数据
        :param y: 真实数据
        :param iteration: 训练次数
        :param eta: 梯度下降
        :return: 返回损失函数
        """
        losses = []
        for i in range(iteration):
            z = self.forward(x)
            L = self.loss(z,y)
            gradient_w,gradient_b = self.gradient(x,y)
            self.update(gradient_w, gradient_b, eta)
            losses.append(L)
            if (i+1) % 10 == 0:
                print("iter {},loss {}".format(i,L))
        return losses

if __name__ == '__main__':
    # 加载数据
    train_data,test_data = load_data()
    x = train_data[:,:-1]
    y = train_data[:,-1:]
    # 创建神经网络
    net = Network(13)
    # 定义训练次数
    num_iteration = 1000
    # 开始训练
    losses = net.train(x,y,num_iteration,eta=0.01)

    # 画出损失函数
    plot_x = np.arange(num_iteration)
    plot_y = np.array(losses)
    plt.plot(plot_x,plot_y)
    plt.show()

